package com.eh.mitakebus;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Created by zhangxiaowei on 17/4/25.
 */

public class MitakeBus {

    private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
        @Override
        protected PostingThreadState initialValue() {
            return new PostingThreadState();
        }
    };
    private static volatile MitakeBus mMitakeBus;
    SubscriberMethodFinder mMethodFinder;
    ConcurrentHashMap<Class<?>, CopyOnWriteArrayList<Subscription>> mSubscription;

    public static MitakeBus getDefaut() {
        if (mMitakeBus == null) {
            synchronized (MitakeBus.class) {
                if (mMitakeBus == null) {
                    mMitakeBus = new MitakeBus();
                }
            }
        }
        return mMitakeBus;
    }

    private MitakeBus() {
        mMethodFinder = new SubscriberMethodFinder();
        mSubscription = new ConcurrentHashMap<>();
    }

    public void register(Object subscriber) {
        Class clazz = subscriber.getClass();
        CopyOnWriteArrayList<Subscription> subscriptions = mSubscription.get(clazz);
        if (subscriptions == null || subscriptions.isEmpty()) {
            subscriptions = new CopyOnWriteArrayList<>();
            List<SubscriberMethod> subscriberMethods = mMethodFinder.getMitakeMethod(clazz);
            for (SubscriberMethod method : subscriberMethods) {
                Subscription subscription = new Subscription(subscriber, method);
                subscriptions.add(subscription);
            }
            mSubscription.put(clazz, subscriptions);
        }

    }

    /**
     * 发送给指定的订阅者
     *
     * @param data
     * @param clazz
     */
    public void post(Object data, Class<?> clazz) {
        PostingThreadState state = currentPostingThreadState.get();
        PostSigleData sigleData = new PostSigleData(data, clazz);
        state.list.add(sigleData);
        if (!state.isPosting) {
            state.isPosting = true;
            try {
                while (!state.list.isEmpty()) {
                    postToSubscripber(state.list.remove(0));
                }
            } finally {
                state.isPosting = false;
            }
        }

    }

    /**
     * 发送给所有订阅者
     *
     * @param data
     */
    public void post(Object data) {
        post(data, null);
    }

    /**
     * 发送给指定订阅者
     */
    private void postToSubscripber(PostSigleData data) {

        if (data.sigleClazz == null) {
            for (Map.Entry<Class<?>, CopyOnWriteArrayList<Subscription>> map : mSubscription.entrySet()) {
                CopyOnWriteArrayList<Subscription> list = map.getValue();
                invokeData(data, list);
            }
        } else {
            CopyOnWriteArrayList<Subscription> list = mSubscription.get(data.sigleClazz);
            invokeData(data, list);
        }


    }

    private void invokeData(PostSigleData data, CopyOnWriteArrayList<Subscription> list) {
        for (Subscription subscription : list) {
            Class<?> clzz = data.data.getClass();
            if (clzz.isAssignableFrom(subscription.subscriberMethod.eventType)) {
                try {
                    subscription.subscriberMethod.method.invoke(subscription.subscriber, data.data);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 解订阅
     *
     * @param subscriber
     */
    public void unRegister(Object subscriber) {
        CopyOnWriteArrayList<Subscription> list = mSubscription.get(subscriber.getClass());
        if (list != null && !list.isEmpty()) {
            for (Subscription subscription : list) {
                subscription.subscriber = null;
                subscription.subscriberMethod.method = null;
                subscription.subscriberMethod.eventType=null;
                subscription.subscriberMethod=null;
                list.remove(subscription);
            }
            list.removeAll(list);
            list = null;
            mSubscription.remove(subscriber.getClass());
        }
    }

    /**
     */
    final static class PostingThreadState {
        final List<PostSigleData> list = new ArrayList<>();
        boolean isPosting;

    }

}
